﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using Microsoft.Practices.Unity;
using Microsoft.Practices.ObjectBuilder2;
using System.Threading;

namespace SmartFront.Toolkit.Core
{
    /// <summary>
    /// Base class for <see cref="LifetimeManager"/>s which want to allow for collection of all objects linked
    /// to lifetime managers of the same type.
    /// </summary>
    /// <remarks>
    /// Derived classes need to expose a static method called <c>DisposeAll</c>.
    /// <example><code>
    /// public static void DisposeAll()
    /// {
    ///    DisposeAll(typeof(...));
    /// }
    /// </code></example>
    /// </remarks>
    public abstract class SelfControlledLifetimeManager : LifetimeManager, IDisposable, IRequiresRecovery
    {
        // Fields
        private readonly object lockObject = new object();
        private object value;

        /// <summary>
        /// Initializes a new instance of the <see cref="SelfControlledLifetimeManager"/> class.
        /// </summary>
        protected SelfControlledLifetimeManager()
        {
            Register(this);
        }

        // Methods
        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);

            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (this.value != null)
            {
                if (this.value is IDisposable)
                {
                    ((IDisposable)this.value).Dispose();
                }
                this.value = null;
            }
        }

        /// <summary>
        /// Gets the value.
        /// </summary>
        /// <returns></returns>
        public override object GetValue()
        {
            Monitor.Enter(this.lockObject);
            if (this.value != null)
            {
                Monitor.Exit(this.lockObject);
            }
            return this.value;
        }

        /// <summary>
        /// Recovers this instance.
        /// </summary>
        public void Recover()
        {
            TryExit(this.lockObject);
        }

        /// <summary>
        /// Removes the value.
        /// </summary>
        public override void RemoveValue()
        {
            this.Dispose();
        }

        /// <summary>
        /// Sets the value.
        /// </summary>
        /// <param name="newValue">The new value.</param>
        public override void SetValue(object newValue)
        {
            this.value = newValue;
            TryExit(this.lockObject);
        }

        /// <summary>
        /// Tries to exit the lock.
        /// </summary>
        /// <param name="lockObj">The lock obj.</param>
        [System.Diagnostics.DebuggerHidden]
        private static void TryExit(object lockObj)
        {
            try
            {
                Monitor.Exit(lockObj);
            }
            catch (SynchronizationLockException)
            {
            }
        }

        private static readonly object syncRoot = new object();
        private static readonly Dictionary<Type, List<LifetimeManager>> lifetimes = new Dictionary<Type, List<LifetimeManager>>();

        /// <summary>
        /// Registers the specified manager.  Allows tracking of all instances so that DisposeAll will work.
        /// </summary>
        /// <param name="manager">The manager.</param>
        private static void Register(SelfControlledLifetimeManager manager)
        {
            lock (syncRoot)
            {
                List<LifetimeManager> list;

                Type managerType = manager.GetType();
                if (!lifetimes.ContainsKey(managerType))
                {
                    list = new List<LifetimeManager>();
                    lifetimes.Add(managerType, list);
                }
                else
                {
                    list = lifetimes[managerType];
                }

                list.Add(manager);
            }
        }

        /// <summary>
        /// Disposes all instances associated with ANY lifetime manager of type T.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "we support both generic and non generic")]
        protected static void DisposeAll<T>()
        {
            DisposeAll(typeof(T));
        }

        /// <summary>
        /// Disposes all instances associated with ANY lifetime manager matching type of <paramref name="managerType"/>.
        /// </summary>
        /// <param name="managerType">Type of the manager.</param>
        protected static void DisposeAll(Type managerType)
        {
            lock (syncRoot)
            {
                if (lifetimes.ContainsKey(managerType))
                {
                    foreach (var item in lifetimes[managerType])
                    {
                        item.SetValue(null);
                        IDisposable disposeItem = item as IDisposable;
                        if (null != disposeItem)
                        {
                            disposeItem.Dispose();
                        }
                    }
                }
            }
        }
    }
}
